/*
 * ClasspathPanel.java
 */

package xq.ide.forms;

import java.awt.Component;
import java.awt.SystemColor;
import java.awt.font.TextAttribute;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import javax.swing.JFileChooser;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import org.apache.log4j.Logger;
import xq.util.FileExtensionFilter;
import xq.util.LoggerUtil;
import static xq.util.CommonUtil.empty;

/**
 *
 * @author  ThorntonRP
 */
public class ClasspathPanel extends javax.swing.JPanel {

	private static final long serialVersionUID = -4410375198234956092L;

	private static final Logger logger = LoggerUtil.getClassLogger();
	private static final ResourceBundle bundle = ResourceBundle.getBundle("xq/ide/forms/Bundle"); // NOI18N

	private List<URL> classpath;
	private boolean ignoreClasspathPropertyChange;

	public ClasspathPanel() {
		classpath = new ArrayList<URL>();
		initComponents();
		addPropertyChangeListener("classpath", new PropertyChangeListener() {

			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				if (!ignoreClasspathPropertyChange) {
					updateClasspathListModel();
				}
			}
		});
		classpathListBox.setCellRenderer(new ClasspathListCellRenderer(classpathListBox.getCellRenderer()));
	}

	public void setClasspath(List<URL> newClasspath) {
		Object oldClasspath = this.classpath;
		this.classpath = newClasspath;
		firePropertyChange("classpath", oldClasspath, newClasspath);
	}

	public List<URL> getClasspath() {
		return Collections.unmodifiableList(classpath);
	}

	private void addClasspathElements() {
		JFileChooser fc = getJarFolderChooser();
		int option = fc.showOpenDialog(this);
		if (JFileChooser.APPROVE_OPTION == option) {
			File[] files = fc.getSelectedFiles();
			if (!empty(files)) {
				int[] indices = new int[files.length];
				int offset = classpath.size();
				for (int i = 0; i < files.length; i++) {
					indices[i] = offset + i;
					classpathListModel.add(indices[i], files[i].getAbsoluteFile());
				}
				updateClasspathListBox(indices);
			}
		}
	}

	private JFileChooser getJarFolderChooser() {
		JFileChooser fc = new JFileChooser();
		fc.setAcceptAllFileFilterUsed(false);
		fc.setApproveButtonText(bundle.getString("_ClasspathPanel.jarFolderChooser.approveButtonText")); // NOI18N
		fc.setDialogTitle(bundle.getString("_ClasspathPanel.jarFolderChooser.dialogTitle")); // NOI18N
		fc.setFileFilter(new FileExtensionFilter("jar|zip", true, "Classpath Entry (folder, ZIP or JAR file)"));
		fc.setFileSelectionMode(javax.swing.JFileChooser.FILES_AND_DIRECTORIES);
		fc.setMultiSelectionEnabled(true);
		return fc;
	}

	private void removeClasspathElements() {
		int[] indices = classpathListBox.getSelectedIndices();
		if (indices.length > 0) {
			for (int pos = indices.length - 1; pos >= 0; pos--) {
				classpathListModel.remove(indices[pos]);
			}
			updateClasspath();
		}
	}

	private void moveClasspathElements(int direction) {
		final int[] indices = classpathListBox.getSelectedIndices();
		if (indices.length > 0) {
			final int lastPos = indices.length - 1;
			if (direction > 0) { // Move down
				final int lastIndex = classpathListModel.size() - direction;
				if (indices[lastPos] < lastIndex) {
					for (int pos = lastPos; pos >= 0; pos--) {
						moveClasspathElement(direction, indices, pos);
					}
					updateClasspathListBox(indices);
				}
			} else if (direction < 0) { // Move up
				final int firstIndex = direction + 1;
				if (indices[0] > firstIndex) {
					for (int pos = 0; pos < indices.length; pos++) {
						moveClasspathElement(direction, indices, pos);
					}
					updateClasspathListBox(indices);
				}
			}
		}
	}

	private void moveClasspathElement(int direction, int[] indices, int pos) {
		File entry = (File) classpathListModel.get(indices[pos]);
		classpathListModel.remove(indices[pos]);
		indices[pos] += direction;
		classpathListModel.add(indices[pos], entry);
	}

	private void updateClasspath() {
		final int size = classpathListModel.size();
		List<URL> newClasspath = new ArrayList<URL>(size);
		for (int i = 0; i < size; i++) {
			addURL(newClasspath, i, (File) classpathListModel.get(i));
		}
		ignoreClasspathPropertyChange = true;
		setClasspath(newClasspath);
		ignoreClasspathPropertyChange = false;
	}

	private void updateClasspathListBox(int[] indices) {
		updateClasspath();
		classpathListBox.setSelectedIndices(indices);
		classpathListBox.scrollRectToVisible(classpathListBox.getCellBounds(
				indices[0], indices[indices.length - 1]));
	}

	private void updateClasspathListModel() {
		classpathListModel.clear();
		for (URL url : classpath) {
			if ("file".equals(url.getProtocol())) {
				try {
					File f = new File(URLDecoder.decode(url.getFile(), "UTF-8"));
					classpathListModel.addElement(f);
					classpathListBox.getCellRenderer();
				} catch (UnsupportedEncodingException ex) {
					logger.warn(ex.toString(), ex);
				}
			} else {
				logger.warn("Classpath resource is not a file: " + url);
			}
		}
	}

	private static void addURL(List<URL> urls, int index, File f) {
		try {
			urls.add(index, f.toURI().toURL());
		} catch (MalformedURLException ex) {
			logger.warn(ex.toString(), ex);
		}
	}

	private static class ClasspathListCellRenderer implements ListCellRenderer {

		private final ListCellRenderer wrapped;
		private Map<TextAttribute, Object> invalidEntryAttributes;

		private ClasspathListCellRenderer(ListCellRenderer cellRenderer) {
			this.wrapped = cellRenderer;
			invalidEntryAttributes = new HashMap<TextAttribute, Object>();
			invalidEntryAttributes.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
		}

		@Override
		public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
			Component c = wrapped.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
			File entry = (File) value;
			if (!entry.exists()) {
				c.setFont(c.getFont().deriveFont(invalidEntryAttributes));
				c.setForeground(SystemColor.textInactiveText);
			}
			return c;
		}
	}

	@SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        bindingGroup = new org.jdesktop.beansbinding.BindingGroup();

        classpathListModel = new javax.swing.DefaultListModel();
        classpathScrollPane = new javax.swing.JScrollPane();
        classpathListBox = new xq.swing.ListBox();
        addPathElementButton = new javax.swing.JButton();
        removePathElementButton = new javax.swing.JButton();
        moveUpPathElementButton = new javax.swing.JButton();
        moveDownPathElementButton = new javax.swing.JButton();

        setName("Form"); // NOI18N

        classpathScrollPane.setName("classpathScrollPane"); // NOI18N

        classpathListBox.setModel(classpathListModel);
        classpathListBox.setName("classpathListBox"); // NOI18N
        classpathScrollPane.setViewportView(classpathListBox);

        java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("xq/ide/forms/Bundle"); // NOI18N
        addPathElementButton.setText(bundle.getString("ClasspathPanel.addPathElementButton.text")); // NOI18N
        addPathElementButton.setToolTipText(bundle.getString("ClasspathPanel.addPathElementButton.toolTipText")); // NOI18N
        addPathElementButton.setActionCommand(bundle.getString("ClasspathPanel.addPathElementButton.actionCommand")); // NOI18N
        addPathElementButton.setName("addPathElementButton"); // NOI18N
        addPathElementButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addPathElementButtonActionPerformed(evt);
            }
        });

        removePathElementButton.setText(bundle.getString("ClasspathPanel.removePathElementButton.text")); // NOI18N
        removePathElementButton.setActionCommand(bundle.getString("ClasspathPanel.removePathElementButton.actionCommand")); // NOI18N
        removePathElementButton.setName("removePathElementButton"); // NOI18N

        org.jdesktop.beansbinding.Binding binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, classpathListBox, org.jdesktop.beansbinding.ELProperty.create("${selectionAvailable}"), removePathElementButton, org.jdesktop.beansbinding.BeanProperty.create("enabled"));
        bindingGroup.addBinding(binding);

        removePathElementButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removePathElementButtonActionPerformed(evt);
            }
        });

        moveUpPathElementButton.setText(bundle.getString("ClasspathPanel.moveUpPathElementButton.text")); // NOI18N
        moveUpPathElementButton.setActionCommand(bundle.getString("ClasspathPanel.moveUpPathElementButton.actionCommand")); // NOI18N
        moveUpPathElementButton.setName("moveUpPathElementButton"); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, classpathListBox, org.jdesktop.beansbinding.ELProperty.create("${selectionAvailable}"), moveUpPathElementButton, org.jdesktop.beansbinding.BeanProperty.create("enabled"));
        bindingGroup.addBinding(binding);

        moveUpPathElementButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveUpPathElementButtonActionPerformed(evt);
            }
        });

        moveDownPathElementButton.setText(bundle.getString("ClasspathPanel.moveDownPathElementButton.text")); // NOI18N
        moveDownPathElementButton.setActionCommand(bundle.getString("ClasspathPanel.moveDownPathElementButton.actionCommand")); // NOI18N
        moveDownPathElementButton.setName("moveDownPathElementButton"); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, classpathListBox, org.jdesktop.beansbinding.ELProperty.create("${selectionAvailable}"), moveDownPathElementButton, org.jdesktop.beansbinding.BeanProperty.create("enabled"));
        bindingGroup.addBinding(binding);

        moveDownPathElementButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveDownPathElementButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(classpathScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 355, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(addPathElementButton)
                    .addComponent(removePathElementButton)
                    .addComponent(moveUpPathElementButton)
                    .addComponent(moveDownPathElementButton))
                .addContainerGap())
        );

        layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {addPathElementButton, moveDownPathElementButton, moveUpPathElementButton, removePathElementButton});

        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(classpathScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 178, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(addPathElementButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(removePathElementButton)
                        .addGap(18, 18, 18)
                        .addComponent(moveUpPathElementButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(moveDownPathElementButton)))
                .addContainerGap())
        );

        bindingGroup.bind();
    }// </editor-fold>//GEN-END:initComponents

private void addPathElementButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addPathElementButtonActionPerformed
	addClasspathElements();
}//GEN-LAST:event_addPathElementButtonActionPerformed

private void removePathElementButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removePathElementButtonActionPerformed
	removeClasspathElements();
}//GEN-LAST:event_removePathElementButtonActionPerformed

private void moveUpPathElementButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveUpPathElementButtonActionPerformed
	moveClasspathElements(-1);
}//GEN-LAST:event_moveUpPathElementButtonActionPerformed

private void moveDownPathElementButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveDownPathElementButtonActionPerformed
	moveClasspathElements(1);
}//GEN-LAST:event_moveDownPathElementButtonActionPerformed
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton addPathElementButton;
    private xq.swing.ListBox classpathListBox;
    private javax.swing.DefaultListModel classpathListModel;
    private javax.swing.JScrollPane classpathScrollPane;
    private javax.swing.JButton moveDownPathElementButton;
    private javax.swing.JButton moveUpPathElementButton;
    private javax.swing.JButton removePathElementButton;
    private org.jdesktop.beansbinding.BindingGroup bindingGroup;
    // End of variables declaration//GEN-END:variables
}
